﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Common;
using System.Data;
using System.Web;
using log4net;
using System.Runtime.CompilerServices;
using System.Threading;

namespace SDL
{
    public partial class ORM
    {
        private readonly ILog log = Log.GetLogger(typeof(ORM));

        protected ConnectionManager _Manager;

        protected DBObject _Object;

        protected Type _Type;

        protected SDLDbConnection _Connection;

        private List<DBObject> _List;

        #region ctor
        internal ORM()
        {
            _Object = this as DBObject;
            _Type = GetType();

            _Manager = new ConnectionManager();
            if (log.IsDebugEnabled) log.Debug("NEW '" + _Type + "', cacheKey: " + this.GetCacheKey());
        }

        public ORM(DBObject oObject)
        {
            _Object = oObject;
            _Type = _Object.GetType();

            _Manager = new ConnectionManager();
            if (log.IsDebugEnabled) log.Debug("NEW '" + _Type + "', cacheKey: " + oObject.GetCacheKey());
        }

        public ORM(Type tType)
        {
            _Type = tType;
            _Manager = new ConnectionManager();
            if (log.IsDebugEnabled) log.Debug("NEW '" + _Type + "', cacheKey: " + this.GetCacheKey());
        }
        #endregion

        private DbCommand CreateCommand(string sCommand, CommandType cmdType)
        {
            _List = null;
            _Connection = _Manager.Connection;
            var myCommand = _Connection.CreateCommand();

            if (cmdType == CommandType.StoredProcedure && sCommand.IndexOf('.') > 0)
            {
                sCommand = "[" + sCommand + "]";
            }
            myCommand.CommandText = sCommand;
            myCommand.CommandType = cmdType;
            return (myCommand);
        }

        public IEnumerable<T> getList<T>() where T : DBObject { return (_List.Cast<T>()); }

        public IEnumerable<DBObject> getList() { return (_List); }

        public T getOne<T>() where T : DBObject { return (_List.Count == 0) ? null : _List[0] as T; }

        public DBObject getOne() { return (_List.Count == 0) ? null : _List[0]; }

        public bool IsCached
        {
            get { return (_Manager.ConnectionString.Cached); }
        }

        #region run
        public virtual void Run(string sCommand)
        {
            Run(sCommand, CommandType.StoredProcedure);
        }

        public virtual void Run(string sCommand, CommandType cmdType)
        {
            Run(sCommand, cmdType, false);
        }

        public virtual void Run(string sCommand, bool putObjectParams)
        {
            Run(sCommand, CommandType.StoredProcedure, putObjectParams);
        }

        public virtual void Run(string sCommand, CommandType cmdType, bool putObjectParams)
        {
            var myCommand = CreateCommand(sCommand, cmdType);

            if (putObjectParams && _Object != null)
            {
                var pms = getFields(_Object);

                foreach (var p in pms)
                {
                    if (_Object.Fields.ContainsKey(p.Key))
                    {
                        _Object.Fields[p.Key] = p.Value;
                    }
                    else
                    {
                        _Object.Fields.Add(p.Key, p.Value);
                    }
                }
            }

            saveParams(myCommand);

            lock (_Type)
            {
                if (IsCached)
                {
                    _List = getCache(myCommand) as List<DBObject>;
                    if (_List != null)
                    {
                        if (log.IsDebugEnabled) log.Debug("CACHE READ COMMAND: '" + myCommand.CommandText + ", commandCacheKey: " + GetCacheKey(myCommand) + "', type: " + myCommand.CommandType);
                        _List.ForEach(i => i.ReadFromCache = true);
                        myCommand.Parameters.Clear();
                        closeReader(myCommand.Connection);
                        return;
                    }
                }

                var lst = new List<DBObject>();
                // try
                //{

                _Connection.Reader = myCommand.ExecuteReader();
                if (log.IsDebugEnabled) log.Debug("SQL EXECUTE: '" + myCommand.CommandText + "', type " + myCommand.CommandType);
                if (_Connection.Reader.HasRows)
                {
                    while (_Connection.Reader.Read())
                    {
                        var data = Activator.CreateInstance(_Type) as DBObject;
                        FillFields(data, _Connection.Reader);
                        data.ReadFromDb = true;
                        lst.Add(data);
                    }
                }
                else if (IsCached)
                {
                    HttpContext.Current.Cache[GetType().ToString()] = new object();
                    if (log.IsDebugEnabled) log.Debug("CACHE CHANGED: " + GetType());
                }
                closeReader(myCommand.Connection);
                _List = lst;

                if (IsCached)
                {
                    saveObjsToCache(_List);
                    setCache(myCommand, _List);
                }
                /*}
                catch (Exception ex)
                {
                    if (log.IsErrorEnabled) log.Error("EXCEPTION: '" + ex.Message);
                    throw;
                }
                finally
                {*/
                myCommand.Parameters.Clear();
                closeReader(myCommand.Connection);
                //}
            }
        }
        #endregion

        #region scalar
        public virtual object Scalar(string sCommand)
        {
            return (Scalar(sCommand, CommandType.StoredProcedure));
        }

        public virtual object Scalar(string sCommand, CommandType cmdType)
        {
            return (Scalar(sCommand, cmdType, false));
        }

        public virtual object Scalar(string sCommand, bool putObjectParams)
        {
            return (Scalar(sCommand, CommandType.StoredProcedure, putObjectParams));
        }

        public virtual object Scalar(string sCommand, CommandType cmdType, bool putObjectParams)
        {
            var myCommand = CreateCommand(sCommand, cmdType);

            if (putObjectParams && _Object != null)
            {
                var pms = getFields(_Object);

                foreach (var p in pms)
                {
                    if (_Object.Fields.ContainsKey(p.Key))
                    {
                        _Object.Fields[p.Key] = p.Value;
                    }
                    else
                    {
                        _Object.Fields.Add(p.Key, p.Value);
                    }
                }
            }

            saveParams(myCommand);

            object oRet;
            //lock (ConnectionManager._Connection)
            // {
            if (IsCached)
            {
                var oCache = getCache(myCommand);
                if (oCache != null)
                {
                    if (log.IsErrorEnabled) log.Error("CACHE READ SCALAR: " + myCommand.CommandText + ", type: " + myCommand.CommandType);
                    myCommand.Parameters.Clear();
                    closeReader(myCommand.Connection);
                    return (oCache);
                }
            }

            oRet = myCommand.ExecuteScalar();

            if (IsCached)
            {
                setCache(myCommand, oRet);
                if (log.IsErrorEnabled) log.Error("CACHE SAVE SCALAR: " + myCommand.CommandText + ", type: " + myCommand.CommandType);
            }
            //}

            myCommand.Parameters.Clear();
            closeReader(myCommand.Connection);
            return (oRet);
        }
        #endregion

        private void saveParams(DbCommand oCmd)
        {
            foreach (var o in _Object.Fields)
            {
                var c = oCmd.CreateParameter();
                c.ParameterName = "@" + o.Key;
                c.Value = o.Value;
                oCmd.Parameters.Add(c);
            }
        }

        public void putParams(object prms)
        {
            var type = prms.GetType();
            var Members = type.GetProperties();
            foreach (var Member in Members)
            {
                var oField = Member.GetValue(prms, null);
                var obj = _Object;
                if (GetType() != typeof(ORM))
                    obj = (this as DBObject);

                if (obj == null) continue;

                if (obj.Fields.ContainsKey(Member.Name))
                    obj.Fields[Member.Name] = oField;
                else
                    obj.Fields.Add(Member.Name, oField);
            }
        }

        internal static void FillFields(DBObject oObj, DbDataReader reader)
        {
            for (var i = 0; i < reader.FieldCount; i++)
            {
                oObj.Fields.Add(reader.GetName(i), reader[i]);
            }
            if (oObj.GetType() == typeof(DBObject)) return;

            FillDataObjectFromReader(oObj, reader);
            oObj.ReadFields = new Dictionary<string, object>(oObj.Fields);
            oObj.Fields.Clear();
        }

        private void closeReader(DbConnection connection)
        {
            ConnectionManager.Free(connection);
        }
    }
}